/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.hiof.moraa.server.service;

import com.hiof.moraa.server.dao.RouteDAO;
import com.hiof.moraa.server.service.model.GeoPoint;
import com.hiof.moraa.server.service.model.MapPoint;
import com.hiof.moraa.mapping.TblRoute;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Bergy
 */
@Transactional(readOnly = true)
public class MapService {

    private static final Logger logger = LoggerFactory.getLogger(MapService.class);
    private ParseKmlFileService parseKmlFileService;
    private HashMap routeMap = new HashMap();
    private RouteDAO routeDAO;
    private FilePathService filePathService;
    private GeometryService geometryService;

    public void setGeometryService(GeometryService geometryService) {
        this.geometryService = geometryService;
    }

    public void setFilePathService(FilePathService filePathService) {
        this.filePathService = filePathService;
    }

    public void setParseKmlFileService(ParseKmlFileService parseKmlFileService) {
        this.parseKmlFileService = parseKmlFileService;
    }

    public void setRouteDAO(RouteDAO routeDAO) {
        this.routeDAO = routeDAO;
    }

    public void initializeMap(int routeId, ServletContext context) throws Exception {
        if (!routeMap.containsKey(routeId)) {
            TblRoute route = routeDAO.getTblRoute(routeId);
            String relativePath = route.getMapPath();
            String absolutePath = filePathService.getAbsolutePath(relativePath, context);
            List<GeoPoint> geoPoints = parseKmlFileService.parseKmlFile(absolutePath);
            routeMap.put(routeId, convert2MapPointList(geoPoints));
        }
    }

    public void initializeMap(int routeId, String contextPath) throws Exception {
        if (!routeMap.containsKey(routeId)) {
            try {
                TblRoute route = routeDAO.getTblRoute(routeId);
                String relativePath = route.getMapPath();
                String absolutePath = contextPath + relativePath;
                List<GeoPoint> geoPoints = parseKmlFileService.parseKmlFile(absolutePath);
                routeMap.put(routeId, convert2MapPointList(geoPoints));
            } catch (Exception ex) {
                logger.error(ex.getMessage());
            }
        }
    }

    public List<MapPoint> getMapPoints(int routeId) {
        return (List<MapPoint>) routeMap.get(routeId);
    }

    public List<MapPoint> getMapPointsInitializeIfNotExist(int routeId, String contextPath) throws Exception {
        List<MapPoint> mapPoints = (List<MapPoint>) routeMap.get(routeId);
        if (mapPoints == null) {
            initializeMap(routeId, contextPath);
            mapPoints = getMapPoints(routeId);
        }
        return mapPoints;
    }

    /**
     * Get truncated geo point with the distance between two points
     * >= 1km: in the case of the route is long
     * >= 100m: in the case of the route is short
     *
     * @param routeId
     * @param context
     * @return
     * @throws Exception
     */
    public List<MapPoint> getTruncatedGeoPointsInitializeIfNotExist(int routeId, ServletContext context) throws Exception {
        List<MapPoint> mapPoints = getMapPointsInitializeIfNotExist(routeId, context);
        return convert2MapPointList(extractTruncatedPoints(mapPoints));
    }

    /**
     * Get truncated geo point with the distance between two points
     * >= 1km: in the case of the route is long
     * >= 100m: in the case of the route is short
     *
     * @param routeId
     * @param contextPath
     * @return
     * @throws Exception
     */
    public List<MapPoint> getTruncatedGeoPointsInitializeIfNotExist(int routeId, String contextPath) throws Exception {
        List<MapPoint> mapPoints = getMapPointsInitializeIfNotExist(routeId, contextPath);
        return convert2MapPointList(extractTruncatedPoints(mapPoints));
    }

    private List<GeoPoint> extractTruncatedPoints(List<MapPoint> mapPoints) {
        List<GeoPoint> truncatedGeoPoints = new ArrayList<GeoPoint>();
        double accumulatedDistance = 0; //used for accumulated the distance
        double thresholdDistance = 0; //used for determine the distance between two point
        if (isLongRoute(mapPoints)) {
            thresholdDistance = 1000;
        } else {
            thresholdDistance = 100;
        }

        for (int i = 0; i < mapPoints.size(); i++) {
            GeoPoint geoPoint = mapPoints.get(i).getGeoPoint();
            //In the case of accumulated distance >= 1km it will be reset to 0
            // and at that time we will add to the truncated list
            if (accumulatedDistance == 0) {
                truncatedGeoPoints.add(geoPoint);
            }

            if (i != mapPoints.size() - 1) {
                GeoPoint nextGeoPoint = mapPoints.get(i + 1).getGeoPoint();
                accumulatedDistance += geometryService.distanceByMeters(geoPoint, nextGeoPoint);
                //reset accumulated distance
                if (accumulatedDistance >= thresholdDistance)
                    accumulatedDistance = 0;
            }
        }
        return truncatedGeoPoints;
    }

    private boolean isLongRoute(List<MapPoint> mapPoints) {
        GeoPoint firstPoint = mapPoints.get(0).getGeoPoint();
        GeoPoint lastPoint = mapPoints.get(mapPoints.size() - 1).getGeoPoint();
        double routeLength = geometryService.distanceByKilometers(firstPoint, lastPoint);
        if(routeLength < 10)
            return false;
        else
            return true;
    }

    private List<MapPoint> getMapPointsInitializeIfNotExist(int routeId, ServletContext context) throws Exception {
        List<MapPoint> mapPoints = (List<MapPoint>) routeMap.get(routeId);
        if (mapPoints == null) {
            initializeMap(routeId, context);
            mapPoints = getMapPoints(routeId);
        }
        return mapPoints;
    }

    public void destroyMap(int routeId) {
        routeMap.remove(routeId);
    }

    public List<Integer> getRunningRouteId() {
        List<Integer> result = new ArrayList<Integer>();
        for (Iterator key = routeMap.keySet().iterator(); key.hasNext(); ) {
            Integer routeId = (Integer) key.next();
            result.add(routeId);
        }
        return result;
    }

    private List<MapPoint> convert2MapPointList(List<GeoPoint> geoPoints) throws Exception {
        List<MapPoint> mapPoints = new ArrayList<MapPoint>();
        double distance = 0;
        for (int i = 0; i < geoPoints.size(); i++) {
            GeoPoint geoPoint = geoPoints.get(i);
            mapPoints.add(new MapPoint(geoPoint, distance));
            if (i != geoPoints.size() - 1) {
                GeoPoint nextGeoPoint = geoPoints.get(i + 1);
                distance += geometryService.distanceByMeters(geoPoint, nextGeoPoint);
            }
        }
        return mapPoints;
    }
}
